#pragma once
#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<cstdlib>
#include<cassert>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

using namespace std;

// 调用该函数就可以将调用者变成守护进程
// 【实现守护进程有很多方法，这里是经典的写法】
void daemonSelf(const char* currpath = nullptr)
{
    //1. 让调用进程忽略掉异常的信号【要脱离shell，那就要屏蔽异常信号，不然会被信号影响】
    signal(SIGPIPE, SIG_IGN);

    //2. 让调用进程不是组长，因为进程组组长无法调用setsid
    if(fork() > 0)
        exit(0); //直接让父进程退出
    // 往下走的肯定是子进程，子进程肯定不是组长，但是子进程还能继承到父进程的代码和资源
    // 所以守护进程也叫精灵进程，本质就是孤儿进程的一种
    pid_t n = setsid(); //成功了返回新的进程组id，其实就是变成新进程组的组长
    assert(n != -1); //返回-1就是失败

    //3. 守护进程要脱离终端， 需要关闭或重定向以前进程默认打开的文件【如0,1,2】
    // 这里最好不要直接将文件描述符生硬的直接关闭掉，因为服务器还是需要一些日志输出。尽管日志输出可以将写到文件里，但是难免会有一些输出到标准输出流的
    // 因此这里可以采用温和一点的做法，这里用文件黑洞——dev/null【所有012的操作都搞到这里，写的话数据直接被丢弃，读也读不到数据】
    // 做了这个操作之后，如果还想看到服务器的日志，可以手动更改日志逻辑，将日志的输出输出到文件中，这样就可以查看日志了
    int fd = open("/dev/null", O_RDWR);
    if(fd >= 0)
    {
        dup2(fd, 0); //将0重定向到fd， 要注意顺序不要反
        dup2(fd, 1);
        dup2(fd, 2);

        close(fd); //0,1,2都指向了fd，fd本身不需要了，再见了
    }
    else
    {
        // 打开失败了，那只能用粗暴的办法了
        close(0);
        close(1);
        close(2);
    }

    //4. 将进程执行路径进行更新【可选】
    if(currpath) //nullptr就说明没选，那就不要更改
        chdir(currpath);
}


